#include <QtGui>
#include <QtXmlPatterns>
#include <QWebView>
#include "brute4qt.h"
#include "queries.h"
#include "ui_brute4qt.h"

/**
    Constructor
    Sets the user interface
    Populates projects and outputs
    @param parent of the window
*/
Brute4Qt::Brute4Qt(QWidget *parent) : QMainWindow(parent),ui(new Ui::Brute4Qt)
{
    ui->setupUi(this);
    QApplication::setStyle(QStyleFactory::create("Plastique"));

    ui->tabWidget->setTabText(ui->tabWidget->indexOf(ui->tab), QApplication::translate("Form", "Tests", 0, QApplication::UnicodeUTF8));
    ui->tabWidget->setTabText(ui->tabWidget->indexOf(ui->tab_2), QApplication::translate("Form", "Outputs", 0, QApplication::UnicodeUTF8));
    ui->tabWidget->setTabText(ui->tabWidget->indexOf(ui->tab_3), QApplication::translate("Form", "Preview", 0, QApplication::UnicodeUTF8));
    ui->tabWidget->setCurrentIndex(0);
    addProjects();
    addOutputs();
    process1 = new QProcess(this);
    process2 = new QProcess(this);
    statusLabel1 = new QLabel("Ready");
    statusLabel2 = new QLabel("Ready");
    statusBar()->addWidget(statusLabel1);
    statusBar()->addWidget(statusLabel2);

    connect(process1, SIGNAL(finished(int)), this, SLOT(stoppedProcess1()));
    connect(process2, SIGNAL(finished(int)), this, SLOT(stoppedProcess2()));

    ui->exportBtn->setEnabled(false);
    ui->runBtn->setEnabled(false);
}

/**
    Deconstructor
    Deletes the ui
*/
Brute4Qt::~Brute4Qt()
{
    delete ui;
}

/**
    Evaluate the xml input
    Converts xml into text and then exports to html
    @param input file - the xml input to be converted
    @param query - the query to be used during the evaluation process
    @see queries.h
    @see getBenchmark
*/
void Brute4Qt::evaluate(const QString &xmlInputFile,const QString &xmlQuery)
{
    QFile sourceDocument;
    sourceDocument.setFileName(xmlInputFile);
    sourceDocument.open(QIODevice::ReadOnly);

    QByteArray outArray;
    QBuffer buffer(&outArray);
    buffer.open(QIODevice::ReadWrite);

    QXmlQuery query;
    query.bindVariable("inputDocument", &sourceDocument);
    query.setQuery(xmlQuery);
    if (!query.isValid())
        return;

    QXmlFormatter formatter(query, &buffer);
    if (!query.evaluateTo(&formatter))
        return;

    buffer.close();

    exportToHtml(xmlInputFile,QString::fromUtf8(outArray.constData()) );
}

/**
    Exports the inputted data to html file
    @param xml file - used for creating the name of the html file, and for running benchmark query
    @param xml evaluation output - the output of the evaluate function
    @see queries.h
*/
void Brute4Qt::exportToHtml(const QString &xmlInputFile, const QString &xmlOutput)
{
    QFileInfo source(xmlInputFile);
    QFile htmlFile(source.absolutePath() + QDir::separator() + source.baseName() + QDateTime::currentDateTime().toString("-ddMMyy-hh-mm-ss")+ ".html");
    source.setFile(htmlFile);

    QString benchmarkResults = getBenchmark(benchmarkQuery,xmlInputFile);

    QTextStream textStream( &htmlFile );

    if( !htmlFile.open( QIODevice::WriteOnly ) )
    {
        qDebug(htmlFile.fileName().toLatin1());
        qDebug("Failed to open file.");
        return;
    }
    QString html_string;
    html_string = xmlOutput;

    //extracting actual and expected values
    QRegExp rx("</Description>");
    rx.setPatternSyntax(QRegExp::Wildcard);
    rx.setMinimal(true);
    rx.setCaseSensitivity(Qt::CaseInsensitive);

    html_string.replace(rx,"</expected>");

    rx.setPattern(" Expected*):");
    html_string.replace(rx,"</actual></td><td><expected>");

    rx.setPattern(" Actual*):");
    html_string.replace(rx,"</Description><td><actual>");

    textStream << mainTable + html_string + "</table> <br></br>" +benchmarkTable+ benchmarkResults + "</table>"<< endl;
    addOutput(source.fileName());
    htmlFile.close();
}

/**
    Runs the benchmark query on the xml file
    @param benchmark query - query used for running the evaluation
    @param xml file - the xml input
    @return string containing the benchmark information
    @see queries.h
    @see evaluate
*/
QString Brute4Qt::getBenchmark(const QString &benchmQuery,const QString &xmlInputFile)
{
    QFile sourceDocument;
    sourceDocument.setFileName(xmlInputFile);
    sourceDocument.open(QIODevice::ReadOnly);

    QByteArray outArray;
    QBuffer buffer(&outArray);
    buffer.open(QIODevice::ReadWrite);

    QXmlQuery query;
    query.bindVariable("inputDocument", &sourceDocument);
    query.setQuery(benchmQuery);
    if (!query.isValid())
        return "";

    QXmlFormatter formatter(query, &buffer);
    if (!query.evaluateTo(&formatter))
        return "";

    buffer.close();
    return QString::fromUtf8(outArray.constData());
}

/**
    Adds projects to the Brute4Qt system
    Scans through the current folder, checking each folder unless it is called compiled_tests or xxx_Brute4Qt_xx
    Add project from each of the scanned folders
*/
void Brute4Qt::addProjects()
{
    QDir directory = QDir(QDir::currentPath());
    QStringList files;
    QString fileName = "[^.]*";
    files = directory.entryList(QStringList(fileName),
                                QDir::Dirs);

    foreach (QString str, files)
    {
        if (str.compare("compiled_tests") !=0 && str.compare("xxx_Brute4Qt_xxx"))
        {
            addProject(QDir::currentPath()+QDir::separator() + str +QDir::separator());
            bool add = true;
            for(int x=0; x<ui->listWidget->count();x++)
            {
                if(ui->listWidget->item(x)->text().contains(str))
                    add = false;
            }
            //if not already in the list
            if(add)
                ui->listWidget->addItem(str);
        }
    }
}

/**
    Adds project to the Brute4Qt system
    Scans the folder and checks if the pro file exists and contains qtestlib statement
    Then it checks if .brute.xml exists, if not, creates it and then reads the folder xml
    @param path of the project folder
*/
void Brute4Qt::addProject(const QString &path)
{
    QDir dir = QDir(path);
    QStringList files;
    QStringList lines;
    QString pro = "*.pro";
    QString xml = ".brute.xml";
    QString proFile;
    QTreeWidgetItem* currentProjectItem;

    files = dir.entryList(QStringList(pro), QDir::Files);
    foreach (QString str,files)
    {
        QFile read(path + str);
        QFileInfo readInfo(read);
        if ( read.open( QIODevice::ReadOnly) )
        {
            QTextStream stream( &read );
            QString line;
            while ( !stream.atEnd() )
            {
                line = stream.readLine(); // line of text excluding '\n'
                lines += line;
            }
            read.close();
        }

        QRegExp rx("*qtestlib*");
        rx.setPatternSyntax(QRegExp::Wildcard);
        rx.setCaseSensitivity(Qt::CaseInsensitive);

        if (lines.indexOf(rx,0) != -1)
        {
            //Test
            qDebug("Found a Test");
            currentProjectItem = new QTreeWidgetItem(ui->treeWidget);
            currentProjectItem->setText(0,readInfo.dir().dirName());

            proFile = str;
        }
        else
        {
            qDebug("NoTest");
            return;
        }
        lines.clear();
    }

    //Checking if brute.xml exists
    files = dir.entryList(QStringList(xml), QDir::Files|QDir::Hidden);
    if (files.size() < 1)
    {
        qDebug("No xml");
        createTestXML(path,proFile );
    }

    //read xml (old or just created)
    files = dir.entryList(QStringList(xml), QDir::Files|QDir::Hidden);
    foreach (QString str,files)
    {
        QFile read(path + str);
        if ( read.open( QIODevice::ReadOnly) )
        {
            qDebug(str.toLatin1());
            //read the xml
            readFolderXML(currentProjectItem, path + str);
        }
        else
            qDebug("Fix XML");
    }
}

/**
    Reads the .brute.xml file
    Checks if the file has a valid structure and adds the information kept in the file to the system
    @param tree widget item - item connected to the project
    @param path of the project file
*/
void Brute4Qt::readFolderXML(QTreeWidgetItem* item, const QString &filename)
{
    QDomDocument doc( "Brute4Qt" );
    QFile file(filename);
    if( !file.open( QIODevice::ReadOnly ) )
    {
        return;
    }
    if( !doc.setContent( &file ) )
    {
        file.close();
        return;
    }

    //check the root of the file
    QDomElement root = doc.documentElement();
    if( root.tagName() != "TestSuite" )
    {
        return;
    }
    QDomNode n = root.firstChild();
    //reads information about the pro file, and adds it to the treeWidget
    QDomElement e = n.toElement();
    if( !e.isNull() )
    {
        if( e.tagName() == "Profile" )
        {
            QTreeWidgetItem* profile = new QTreeWidgetItem(ui->treeWidget->topLevelItem(ui->treeWidget->indexOfTopLevelItem(item)));
            profile->setText(0,"Profile");
            QTreeWidgetItem* value = new QTreeWidgetItem(profile);
            value->setText(0,e.attribute("value",""));
        }
    }

    n = n.nextSibling();
    e = n.toElement();
    //reads information about outputs of the project, and adds them to the tree
    if( !e.isNull() )
    {
        if( e.tagName() == "Outputs" )
        {
            QTreeWidgetItem* outputs = new QTreeWidgetItem(ui->treeWidget->topLevelItem(ui->treeWidget->indexOfTopLevelItem(item)));
            outputs->setText(0,"Outputs");
            QTreeWidgetItem* value;
            QString concOutput = e.text();
            QStringList outputsList = concOutput.split(",");
            foreach(QString out, outputsList)
            {
                if(out.length()>1)
                {
                    value = new QTreeWidgetItem(outputs);
                    value->setText(0,out);
                }
            }
        }
    }
    file.close();
}

/**
    Add outputs to the second tab
    Loops through the files in the compiled_test folder, and adds them to the list
*/
void Brute4Qt::addOutputs()
{
    QDir directory = QDir(QDir::currentPath()+ QDir::separator() + "compiled_tests");
    QStringList files;
    QString fileName = "[^.]*.html";
    files = directory.entryList(QStringList(fileName),
                                QDir::Files);
    ui->listWidget_2->clear();
    foreach (QString str, files)
    {
        ui->listWidget_2->addItem(str);
    }
}

/**
    Runs the specified program with arguments
    Checks if at least one of the processes is ready
    @param progam which needs to be run
    @param arguments for the program
*/
void Brute4Qt::runProgram(QString &prog, QStringList args, QString &path,bool output,bool xml)
{
    if (process1->state() == 0) //not running
    {
        if(output)
        {
            disconnect(process1, SIGNAL(finished(int)), this, SLOT(outputProcess1()));
            connect(process1, SIGNAL(finished(int)), this, SLOT(outputProcess1()));
        }
        else
        {
            disconnect(process1, SIGNAL(finished(int)), this, SLOT(outputProcess1()));
        }

        if(xml)
        {
            disconnect(process1, SIGNAL(finished(int)), this, SLOT(processXml1()));
            connect(process1, SIGNAL(finished(int)), this, SLOT(processXml1()));
        }
        else
        {
            disconnect(process1, SIGNAL(finished(int)), this, SLOT(processXml1()));
        }

        process1->setWorkingDirectory(path);
        runningProcess1();
        if (args.empty())
            process1->start(prog);
        else
            process1->start(prog,args);

        if (!process1->waitForStarted())
        {
            qDebug("Problem with starting process 1");
            return;
        }
    }
    else if (process2->state() == 0) //not running
    {
        if(output)
        {
            disconnect(process2, SIGNAL(finished(int)), this, SLOT(outputProcess2()));
            connect(process2, SIGNAL(finished(int)), this, SLOT(outputProcess2()));
        }
        else
        {
            disconnect(process2, SIGNAL(finished(int)), this, SLOT(outputProcess2()));
        }

        if(xml)
        {
            disconnect(process2, SIGNAL(finished(int)), this, SLOT(processXml2()));
            connect(process2, SIGNAL(finished(int)), this, SLOT(processXml2()));
        }
        else
        {
            disconnect(process2, SIGNAL(finished(int)), this, SLOT(processXml2()));
        }

        process2->setWorkingDirectory(path);
        runningProcess2();
        if (args.empty())
            process2->start(prog);
        else
            process2->start(prog,args);

        if (!process2->waitForStarted())
        {
            qDebug("Problem with starting process 2");
            return;
        }
    }
    else //all running
    {
        if (!process1->waitForFinished())
        {
            qDebug("Problem with stoping process 1");
            return;
        }
        runProgram(prog,args,path,output);
    }
}

/**
    Runs when the compile button is pressed
    Runs the qmake and mingw32 processes
*/
void Brute4Qt::on_compileBtn_clicked()
{
    if (ui->listWidget->currentRow() == -1)
        return;
    QString path = QDir::currentPath()+ QDir::separator() + ui->listWidget->currentItem()->text();
    QString prog = "qmake";
    //we need to wait for it to finish before running the next step
    QProcess proc(this);
    connect(&proc, SIGNAL(finished(int)), this, SLOT(stoppedProcess1()));
    proc.setWorkingDirectory(path);
    runningProcess1();
    proc.start(prog);
    if (!proc.waitForStarted())
    {
        qDebug("Problem with starting qmake");
        return;
    }
    proc.closeWriteChannel();
    if (!proc.waitForFinished())
    {
        qDebug("Problem with stoping qmake");
        return;
    }

    prog = "mingw32-make";
    runProgram(prog, QStringList(),path);
}

/**
    Notifies the user that a process is running
*/
void Brute4Qt::runningProcess2()
{
    //qDebug("Running");
    statusLabel2->setText("Running");
    //   qApp->processEvents();
}

/**
    Notifies the user that a process has finished running
*/
void Brute4Qt::stoppedProcess2()
{
    //qDebug("Done");
    statusLabel2->setText("Ready");
    updateTabs();
}

/**
    Displays output of the process
*/
void Brute4Qt::outputProcess2()
{
    QMessageBox::information(this,"Program output (2)",process2->readAllStandardOutput());
}

/**
    Takes the output of the process and passes to html converter
*/
void Brute4Qt::processXml2()
{
    QString output = process2->readAllStandardOutput();

    QFile file( QDir::currentPath()+ QDir::separator() + "compiled_tests"+QDir::separator()+ui->listWidget->currentItem()->text().toLatin1()+".xml");
    if(file.exists())
    {
        file.remove();
    }
    QFileInfo out_xml(file);

    if ( file.open( QIODevice::WriteOnly ) )
    {
        QTextStream stream( &file );
        stream << output << endl;
    }
    evaluate(out_xml.absoluteFilePath(), this->mainQuery);
}

/**
    Takes the output of the process and passes to html converter
*/
void Brute4Qt::processXml1()
{
    QString output = process1->readAllStandardOutput();

    QFile file( QDir::currentPath()+ QDir::separator() + "compiled_tests"+QDir::separator()+ui->listWidget->currentItem()->text().toLatin1()+".xml");
    if(file.exists())
    {
        file.remove();
    }
    QFileInfo out_xml(file);

    if ( file.open( QIODevice::WriteOnly ) )
    {
        QTextStream stream( &file );
        stream << output << endl;
    }
    evaluate(out_xml.absoluteFilePath(), this->mainQuery);
}

/**
    Displays output of the process
*/
void Brute4Qt::outputProcess1()
{
    QMessageBox::information(this,"Program output (1)",process1->readAllStandardOutput());
}

/**
    Notifies the user that a process is running
*/
void Brute4Qt::runningProcess1()
{
    //qDebug("Running");
    statusLabel1->setText("Running");
    //   qApp->processEvents();
}

/**
    Notifies the user that a process has finished running
*/
void Brute4Qt::stoppedProcess1()
{
    //qDebug("Done");
    statusLabel1->setText("Ready");
    updateTabs();
}

/**
    Runs when the "open in browser" button is pressed
    Opens the selected item in the system default browser
*/
void Brute4Qt::on_browserBtn_clicked()
{
    if (ui->listWidget_2->currentRow() == -1)
        return;
    QDesktopServices::openUrl(QUrl("compiled_tests\\" + ui->listWidget_2->currentItem()->text()));
}

/**
    Runs when the "open" button is pressed
    Opens the selected item in the third tab
*/
void Brute4Qt::on_openBtn_clicked()
{
    if (ui->listWidget_2->currentRow() == -1)
        return;

    ui->tabWidget->setCurrentIndex(2);
    ui->webView->setUrl(QUrl(QDir::currentPath()+QDir::separator()+"compiled_tests"+QDir::separator()+ui->listWidget_2->currentItem()->text()));
}

/**
    Runs when the "run" button is pressed
    Runs the test suite and displays the output
*/
void Brute4Qt::on_runBtn_clicked()
{
    QFile executable(QDir::currentPath()+ QDir::separator() + "compiled_tests"+QDir::separator()+ui->listWidget->currentItem()->text().toLatin1()+".exe");
    QFileInfo execInfo(executable);
    if (!executable.exists())
    {
        emit on_compileBtn_clicked();
    }
    else
    {
        QString prog = execInfo.absoluteFilePath();
        QString path = execInfo.absoluteDir().absolutePath();
        runProgram(prog,QStringList(),path,true);
    }

}

/**
    Runs when the "refresh" button is pressed
    Refreshes the outputs tab
*/
void Brute4Qt::on_refreshBtn_clicked()
{
    addOutputs();
}

/**
    Runs when the "Export to html" button is pressed
    Runs the test suite and exports the output to a html file
*/
void Brute4Qt::on_exportBtn_clicked()
{
    QFile executable(QDir::currentPath()+ QDir::separator() + "compiled_tests"+QDir::separator()+ui->listWidget->currentItem()->text().toLatin1()+".exe");
    QFileInfo execInfo(executable);
    if (!executable.exists())
    {
        emit on_compileBtn_clicked();
    }
    else
    {
        QString sCmd=  execInfo.absoluteFilePath();
        QString path = execInfo.absoluteDir().absolutePath();

        runProgram(sCmd,QStringList("-xml"),path,false,true);
    }
}

/**
    Updates the information displayed in the tabs
    Checks if the projects have been compiled
*/
void Brute4Qt::updateTabs()
{
    //qDebug("now");
    int x = ui->tabWidget->currentIndex();
    switch (x)
    {
    case 0:
        {
            if (ui->listWidget->currentRow() == -1)
            {
                if (ui->listWidget->count() > 0)
                {
                    ui->listWidget->setCurrentRow(0);
                }
                else
                    return;

                if (ui->treeWidget->topLevelItemCount() > 0)
                {
                    ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(0),0);
                }
                else
                    return;
            }
            //Check if the executable for the project exists
            QFile file( QDir::currentPath()+ QDir::separator() + "compiled_tests"+QDir::separator()+ui->listWidget->currentItem()->text().toLatin1()+".exe");
            if (!file.exists())
            {
                ui->exportBtn->setEnabled(false);
                ui->runBtn->setEnabled(false);
            }
            else
            {
                ui->exportBtn->setEnabled(true);
                ui->runBtn->setEnabled(true);
            }

            ui->treeWidget->clear();
            //listWidget->clear();
            addProjects();
        }
        break;
    case 1:
        {
            emit on_refreshBtn_clicked();
        }
        break;
    case 2:
        {
        }
        break;
    }
}

/**
    Runs when the user changes the current row of the list Widget
*/
void Brute4Qt::on_listWidget_currentRowChanged(int currentRow)
{
    updateTabs();
}

/**
    Runs when the user changes the current tab
*/
void Brute4Qt::on_tabWidget_currentChanged(int index)
{
    updateTabs();
}

/**
    Creates .brute.xml file for a project
    @param path of the project
    @param name of the .pro file
*/
void Brute4Qt::createTestXML(const QString &path,const QString &profile)
{
    QDomDocument doc( "Brute4Qt" );
    QDomElement root = doc.createElement( "TestSuite" );
    root.setAttribute("name", QFileInfo(path).dir().dirName());
    root.setAttribute("path", path);
    doc.appendChild( root );

    root.appendChild( addNode( doc, "Profile", profile ) );
    root.appendChild( addNode( doc, "Outputs" ) );
    root.appendChild( addNode( doc, "Functions" ) );

    QFile file( path + ".brute.xml");
    if( !file.open( QIODevice::WriteOnly ) )
        return;
    QFileInfo fileInf(file);
#ifdef Q_WS_WIN
    QString hideFileCommand;
    hideFileCommand = "attrib +h " + path + fileInf.fileName();
    system(hideFileCommand.toLatin1());
#endif

#ifdef Q_WS_X11
    //add code
#endif

    QTextStream ts( &file );
    ts << doc.toString();

    file.close();
}

/**
    Creates a xml node
    @param document which is going to be altered
    @param name of the node
    @param attribute for the node
    @return created node
*/
QDomElement Brute4Qt::addNode( QDomDocument &document, const QString &nodeName, const QString &attribute )
{
    QDomElement createdNode = document.createElement( nodeName );
    if (attribute.length() > 0)
    {
        createdNode.setAttribute("value", attribute);
    }
    //QDomText a = d.createTextNode("asdsa");
    //cn.appendChild(a);
    return createdNode;
}

/**
    Adds output file to a project xml file
    @param Name of the output file
*/
void Brute4Qt::addOutput(const QString &outputFileName)
{
    QDomDocument doc( "Brute4Qt" );

    QFile file(QDir::currentPath()+ QDir::separator() + ui->listWidget->currentItem()->text().toLatin1() + QDir::separator()+ ".brute.xml");
    if( !file.open( QIODevice::ReadWrite ) )
    {
        return;
    }
    if( !doc.setContent( &file ) )
    {
        file.close();
        return;
    }

    file.reset();

    QDomElement root = doc.documentElement();
    if( root.tagName() != "TestSuite" )
    {
        return;
    }
    QDomNode node = root.firstChild();
    node = node.nextSibling();
    QDomElement element = node.toElement();
    if( !element.isNull() )
    {
        if( element.tagName() == "Outputs" )
        {
            qDebug("Add output");
            QDomText createdNode = doc.createTextNode( outputFileName + "," );
            element.appendChild(createdNode);
        }
    }

    QTextStream ts(&file);
    ts << doc.toString();
    file.close();
}
